今天來介紹這個空氣盒子專案,細部運作原理。
我們這理用到很多模組是 UART 通訊介面,但是實際硬體上只有一個 UART 介面。
那我們這邊使用SoftwareSerial來克服這個問題。
那我們先來逐一介紹程式邏輯。
#include <FS.h>
#include <SPI.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
//needed for library
#include <DNSServer.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h> //https://github.com/alanswx/ESPAsyncWiFiManager
AsyncWebServer server(80);
DNSServer dns;
AsyncWiFiManager wifiManager(&server,&dns);
#include <WebSocketsClient.h>
#include <Hash.h>
那這邊可以看到使用到的是WiFiManager to ESP Async Server。
WiFiManager 方便的介面程式庫。
WiFiManager 是一個方便的 WiFi 管理介面,並且可以儲存額外資訊在硬體內。
程式碼再來是一些網路協定的程式庫。
這邊就是 PMS5003 空氣偵測 是用 SoftwareSerial,程式庫且只用到 RX 接收的功能使用 D4 接腳。
#include <SoftwareSerial.h>
//RX ,TX,false,buffer
//D4=2 PMS5003
SoftwareSerial swSer(2, SW_SERIAL_UNUSED_PIN, false, 64);
這邊是藍牙部分也是用 SoftwareSerial,程式庫且使用 RX => D7 TX=> D8 接腳。
//RX ,TX,false,buffer
//D7=13,D8=15 BLE
SoftwareSerial ble(13, 15, false, 128);
int BLE_stat_PIN=10;
這邊是 HMI 顯示模組部分也是用 SoftwareSerial,程式庫且使用 RX => D6 TX=> D5 接腳。
//RX ,TX,false,buffer
//D6=12,D5=14 HMI
SoftwareSerial swSer3(12, 14, false, 128);
#include <Nextion.h>
Nextion myNextion(swSer3, 9600);
這邊是 HTU21D 溫絲度部分。
int Ghostflag=1;
float dht_h = 0;
float dht_t = 0;
float dht_f = 0;
float temperature[10];
float humidity[10];
#include <Wire.h>
#include "SparkFunHTU21D.h"
HTU21D myHumidity;
這邊就是一些基本變數與設定值 的定義部分。
#define LENG 31 //0x42 + 31 bytes equal to 32 bytes
unsigned char buf[LENG];
int cfPM1_0Value=0;
int cfPM2_5Value=0;
int cfPM10Value=0;
int atPM1_0Value=0;
int atPM2_5Value=0;
int atPM10Value=0;
int PN03Value=0; //0.1升空氣中直徑在 0.3um 的顆粒物個數
int PN05Value=0; //0.1升空氣中直徑在 0.5um 的顆粒物個數
int PN10Value=0; //0.1升空氣中直徑在 1.0um 的顆粒物個數
int PN25Value=0; //0.1升空氣中直徑在 2.5um 的顆粒物個數
int PN50Value=0; //0.1升空氣中直徑在 5.0um 的顆粒物個數
int PN100Value=0; //0.1升空氣中直徑在 10um 的顆粒物個數
int pm1_0[10];
int pm2_5[10];
int pm10[10];
int pm_index=0;
#define SWITCH_PIN 0//switch
char password[32];
char domain[30] = "zzzzzz";
char productId[17] = "zzzzzzzz";
WebSocketsClient webSocket;
bool wsAuthCheck = false;
bool wsAuthStatus = false;
char rid[41];
#define WS_NONE 9999
#define WS_GET_RID 100
int wsProcess = WS_NONE;
char SendData[384];
unsigned long error;
const int SENSOR_INTERVAL = 1000;
const int BLE_INTERVAL = 2000;
long unsigned int prevBleTime = 0;
long unsigned int prevSensorTime = 0;
//flag for saving data
bool shouldSaveConfig = false;
//callback notifying us of the need to save config
void saveConfigCallback () {
Serial.println("Should save config");
shouldSaveConfig = true;
}
起始設定區塊,包含各個硬體的起始與 wifiManager 區塊。
void setup()
{
Serial.begin(115200);
swSer.begin(9600);
myNextion.init();
pinMode(BLE_stat_PIN, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(SWITCH_PIN, INPUT);
myHumidity.begin();
BLESetup();
FSread();
Serial.println("\nStart Up: PAQman");
wificonnect(60);
prevSensorTime = millis();
webSocket.begin(String(domain), 80, "/ws");
webSocket.onEvent(webSocketEvent);
}
void FSread()
{
//clean FS, for testing
// SPIFFS.format();delay(100);
//read configuration from FS json
Serial.println("mounting FS...");
if (SPIFFS.begin()) {
Serial.println("mounted file system");
if (SPIFFS.exists("/config.json")) {
//file exists, reading and loading
Serial.println("reading config file");
File configFile = SPIFFS.open("/config.json", "r");
if (configFile) {
Serial.println("opened config file");
size_t size = configFile.size();
// Allocate a buffer to store contents of the file.
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.parseObject(buf.get());
json.printTo(Serial);
if (json.success()) {
Serial.println("\nparsed json");
strcpy(cik, json["cik"]);
strcpy(domain, json["domain"]);
strcpy(productId, json["productId"]);
} else {
Serial.println("failed to load json config");
}
}
}
} else {
Serial.println("failed to mount FS");
}
}
void saveConfig()
{
Serial.println("saving config");
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
json["cik"] = cik;
json["domain"] = domain;
json["productId"] = productId;
File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) {
Serial.println("failed to open config file for writing");
}
json.printTo(Serial);
json.printTo(configFile);
configFile.close();
Serial.println("");
}
void wificonnect(int timeOut)
{
AsyncWiFiManagerParameter custom_cik("cik", "cik", cik, 41);
AsyncWiFiManagerParameter custom_product_id("productId", "productId", productId, 17);
AsyncWiFiManagerParameter custom_domain("domain", "domain", domain, 31);
//set config save notify callback
wifiManager.setSaveConfigCallback(saveConfigCallback);
//add all your parameters here
wifiManager.addParameter(&custom_cik);
wifiManager.addParameter(&custom_product_id);
wifiManager.addParameter(&custom_domain);
wifiManager.setTimeout(timeOut);
String ssid = "PAQ-MAN" + String(ESP.getChipId());
Serial.println("Please connected to ssid:" + ssid);
if (!wifiManager.autoConnect(ssid.c_str(), "88888888")) {
Serial.println("failed to connect and hit timeout");
delay(3000);
//reset and try again, or maybe put it to deep sleep
// ESP.reset();
// delay(5000);
} else {
Serial.println("connected...yeey :)");
Serial.print("local ip:");
Serial.println(WiFi.localIP());
strcpy(cik, custom_cik.getValue());
strcpy(productId, custom_product_id.getValue());
strcpy(domain, custom_domain.getValue());
//save the custom parameters to FS
if (shouldSaveConfig) {
saveConfig();
}
}
}
這部分就是藍芽起始設定部分,這部分會主要是在開機時先定義藍芽裝置名稱。
void BLESetup()
{
ble.begin(9600);
if (digitalRead(BLE_stat_PIN) == 0) {
Serial.println("BLE Setup");
delay(200);
String BLEname = "PAQ-MAN" + String(ESP.getChipId());
ble.println("AT+NAME");
delay(200);
if (ble.available()) {
String inData = ble.readStringUntil('\n');
Serial.print(inData);
if (inData != String("+NAME=" + BLEname)) {
ble.println("AT+NAME" + BLEname );
delay(200);
if (ble.available()) {
inData = ble.readStringUntil('\n');
Serial.print(inData);
}
}
}
}
}
這邊定義一個供輸入的按鈕偵測,如果有按下就會重置 Wifi。
//=================
void CheckWifiSwitch(void)
//=================
{
if(!digitalRead(SWITCH_PIN))
{
int i=0,j=0;
for(i=0;i<30;i++)
{
delay(100);
if(!digitalRead(SWITCH_PIN))
j++;
else
i=30;
}
if(j>25)
{
Serial.println("RESETING WiFi SSID/PASSWORD/DEVICE NAME");
digitalWrite(LED_BUILTIN, LOW);
wifiManager.resetSettings();
wificonnect(120);
}
}
}
這部分是 讀取溫濕度感應器與空氣品質數值,並且更新資訊到 HMI 去做顯示。
void getSensorData()
{
dht_h = myHumidity.readHumidity();
// Read temperature as Celsius (the default)
dht_t = myHumidity.readTemperature();
// Read temperature as Fahrenheit (isFahrenheit = true)
// dht_f = myHumidity.readTemperature(true);
if (!isnan(dht_h) && !isnan(dht_t) && !isnan(dht_f)) {
myNextion.setComponentText("temperature",String(dht_t));
myNextion.setComponentText("humidity",String(dht_h));
}
if(swSer.find(0x42)){ //start to read when detect 0x42
swSer.readBytes(buf,LENG);
if(buf[0] == 0x4d){
if(checkValue(buf,LENG)){
cfPM1_0Value=transmitData(buf,3,4);//標準顆粒物PM01
cfPM2_5Value=transmitData(buf,5,6);//標準顆粒物PM2.5
cfPM10Value=transmitData(buf,7,8);//標準顆粒物PM10
atPM1_0Value=transmitData(buf,9,10);//大氣環境下PM01
atPM2_5Value=transmitData(buf,11,12);//大氣環境下PM2.5
atPM10Value=transmitData(buf,13,14);//大氣環境下PM10
PN03Value=transmitData(buf,15,16);//0.1升空氣中直徑在 0.3um 的顆粒物個數
PN05Value=transmitData(buf,17,18);//0.1升空氣中直徑在 0.5um 的顆粒物個數
PN10Value=transmitData(buf,19,20);//0.1升空氣中直徑在 1.0um 的顆粒物個數
PN25Value=transmitData(buf,21,22);//0.1升空氣中直徑在 2.5um 的顆粒物個數
PN50Value=transmitData(buf,23,24);//0.1升空氣中直徑在 5.0um 的顆粒物個數
PN100Value=transmitData(buf,25,26);//0.1升空氣中直徑在 10um 的顆粒物個數
}
}
myNextion.setComponentValue("pm1_0",cfPM1_0Value);
myNextion.setComponentValue("pm2_5",cfPM2_5Value);
myNextion.setComponentValue("pm10",cfPM10Value);
pm1_0[pm_index]=cfPM1_0Value;
pm2_5[pm_index]=cfPM2_5Value;
pm10[pm_index]=cfPM10Value;
if (!isnan(dht_h) && !isnan(dht_t) && !isnan(dht_f)) {
temperature[pm_index] = dht_t;
humidity[pm_index] = dht_h;
} else {
temperature[pm_index] = 0;
humidity[pm_index] = 0;
}
pm_index++;
}
}
這部分就是將收集到的全部資訊,以 JSON 格式送到藍芽。
void sendToBLE()
{
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["pm1_0"] = pm1_0[pm_index];
root["pm2_5"] = pm2_5[pm_index];
root["pm10"] = pm10[pm_index];
root["temperature"] = dht_t;
root["humidity"] = dht_h;
root["PN03Value"] = PN03Value;
root["PN05Value"] = PN05Value;
root["PN10Value"] = PN10Value;
root["PN25Value"] = PN25Value;
root["PN50Value"] = PN50Value;
root["PN100Value"] = PN100Value;
char dataBuffer[256];
root.printTo(dataBuffer, sizeof(dataBuffer));
Serial.println(dataBuffer);
ble.println(dataBuffer);
}
這部分是將收到的全部資訊以 websocket 送到公司平台,可以將這部分改成 MQTT 送到 LASS。
void getDataJsonString() {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonArray& data1_0 = root.createNestedArray("pm1_0");
JsonArray& data2_5 = root.createNestedArray("pm2_5");
JsonArray& data10 = root.createNestedArray("pm10");
JsonArray& dataTemperature = root.createNestedArray("temperature");
JsonArray& dataHumidity = root.createNestedArray("humidity");
for ( int j = 0; j < 10; ++j ){
data1_0.add(pm1_0[j]);
data2_5.add(pm2_5[j]);
data10.add(pm10[j]);
dataTemperature.add(temperature[j]);
dataHumidity.add(humidity[j]);
}
char dataBuffer[256];
root.printTo(dataBuffer, sizeof(dataBuffer));
Serial.println(dataBuffer);
pm_index=0;
DynamicJsonBuffer jsonBuffer2;
JsonObject& root2 = jsonBuffer2.createObject();
JsonArray& calls = root2.createNestedArray("calls");
JsonObject& call = calls.createNestedObject();
call["id"] = 999;
call["procedure"] = "write";
JsonArray& arguments = call.createNestedArray("arguments");
JsonObject& alias = arguments.createNestedObject();
alias["alias"] = "raw_data";
arguments.add(dataBuffer);
root2.printTo(SendData, sizeof(SendData));
if (!digitalRead(BLE_stat_PIN)) {
Serial.println(SendData);
webSocket.sendTXT(SendData);
}
}
這就是下面主要邏輯運行的部分。
void loop() {
CheckWifiSwitch();
if (prevSensorTime + SENSOR_INTERVAL < millis() || millis() < prevSensorTime){
digitalWrite(LED_BUILTIN, LOW);
getSensorData();
if (pm_index >= 10) {
getDataJsonString();
}
if (Ghostflag == 1)
{
myNextion.sendCommand(String("p1.pic=2").c_str());
Ghostflag = 2;
} else {
myNextion.sendCommand(String("p1.pic=1").c_str());
Ghostflag = 1;
}
prevSensorTime = millis();
}
digitalWrite(LED_BUILTIN, HIGH);
//====================================
if (prevBleTime + BLE_INTERVAL < millis() || millis() < prevBleTime){
if (digitalRead(BLE_stat_PIN)) {
sendToBLE();
}
prevBleTime = millis();
}
//====================================
if (!digitalRead(BLE_stat_PIN)) {
if (WiFi.status() == WL_CONNECTED) {
webSocket.loop();
}
}
delay(100);
}
char checkValue(unsigned char *thebuf, char leng)
{
char receiveflag=0;
int receiveSum=0;
for(int i=0; i<(leng-2); i++){
receiveSum=receiveSum+thebuf[i];
}
receiveSum=receiveSum + 0x42;
if(receiveSum == ((thebuf[leng-2]<<8)+thebuf[leng-1])) //check the serial data
{
receiveSum = 0;
receiveflag = 1;
}
return receiveflag;
}
int transmitData(unsigned char *thebuf,int HighB,int LowB)
{
int result;
result=((thebuf[HighB]<<8) + thebuf[LowB]); //count PM1.0 value of the air detector module
return result;
}
今天介紹空氣盒子專案實程式碼部分說明與講解。